package com.woodblockwithoutco.remotecontrollerexample;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.MediaMetadataEditor;
import android.media.MediaMetadataRetriever;
import android.media.RemoteController;
import android.media.RemoteController.MetadataEditor;
import android.media.RemoteController.OnClientUpdateListener;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.view.KeyEvent;

public class TransportControlService extends NotificationListenerService implements OnClientUpdateListener {

    //Binder for our service.
    private IBinder mBinder = new RCBinder();
    
    private RemoteController mRemoteController;
    private Context mContext;
    private AudioManager mAudioManager;
    
    //external callback provided by user.
    private TransportControlView.OnClientUpdateListener mExternalClientUpdateListener;
    
    @Override
    public IBinder onBind(Intent intent) {
        if(intent.getAction().equals("com.woodblockwithoutco.remotecontrollerexample.BIND_RC_CONTROL_SERVICE")) {
            return mBinder;
        } else {
            return super.onBind(intent);
        }
    }
    
    @Override
    public void onNotificationPosted(StatusBarNotification notification) {
    }

    @Override
    public void onNotificationRemoved(StatusBarNotification notification) {
    }
    
    @Override
    public void onCreate() {
        //saving the context for further reuse
        mContext = getApplicationContext();
        mAudioManager = (AudioManager)mContext.getSystemService(AUDIO_SERVICE);
        mRemoteController = new RemoteController(mContext, this);
    }
    
    @Override
    public void onDestroy() {
        setRemoteControllerDisabled();
    }
    
    // Following method will be called by Widget using IBinder
    
    public boolean setArtworkConfiguration(int width, int height) {
        return mRemoteController.setArtworkConfiguration(width, height);
    }
    
    public String getRemoteControlClientPackageName() {
        Method method = ReflectionUtils.findMethod(RemoteController.class, "getRemoteControlClientPackageName");
        return (String) ReflectionUtils.invokeMethod(method, mRemoteController);
    }
    
    /**
     * Enables the RemoteController thus allowing us to receive metadata updates.
     */
    public boolean setRemoteControllerEnabled() {
        return mAudioManager.registerRemoteController(mRemoteController);
    }
    
    /**
     * Disables RemoteController.
     */
    public void setRemoteControllerDisabled() {
        mAudioManager.unregisterRemoteController(mRemoteController);
    }
    
    /**
     * Sets up external callback for client update events.
     * @param listener External callback.
     */
    public void setClientUpdateListener(TransportControlView.OnClientUpdateListener listener) {
        mExternalClientUpdateListener = listener;
    }
    
    /**
     * Sends "next" media key press.
     */
    public void sendNextKey() {
        sendKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT);
    }
    
    /**
     * Sends "previous" media key press.
     */
    public void sendPreviousKey() {
        sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
    }
    
    /**
     * Sends "pause" media key press, or, if player ignored this button, "play/pause".
     */
    public void sendPauseKey() {
        if(!sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE)) {
            sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
        }
    }
    
    /**
     * Sends "play" button press, or, if player ignored it, "play/pause".
     */
    public void sendPlayKey() {
        if(!sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY)) {
            sendKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
        }
    }
    
    /**
     * @return Current song position in milliseconds.
     */
    public long getEstimatedPosition() {
        return mRemoteController.getEstimatedMediaPosition();
    }
    
    /**
     * Seeks to given position.
     * @param ms Position in milliseconds.
     */
    public void seekTo(long ms) {
        mRemoteController.seekTo(ms);
    }
    //end of Binder methods.
    
    //helper methods

    //this method let us avoid the bug in RemoteController
    //which results in Exception when calling RemoteController#setSynchronizationMode(int)
    //doesn't seem to work though
    private void setSynchronizationMode(RemoteController controller, int sync) {
        if ((sync != RemoteController.POSITION_SYNCHRONIZATION_NONE) && (sync != RemoteController.POSITION_SYNCHRONIZATION_CHECK)) {
            throw new IllegalArgumentException("Unknown synchronization mode " + sync);
        }
        
        Class<?> iRemoteControlDisplayClass;
        
        try {
            iRemoteControlDisplayClass  = Class.forName("android.media.IRemoteControlDisplay");
        } catch (ClassNotFoundException e1) {
            throw new RuntimeException("Class IRemoteControlDisplay doesn't exist, can't access it with reflection");
        }
        
        Method remoteControlDisplayWantsPlaybackPositionSyncMethod;
        try {
            remoteControlDisplayWantsPlaybackPositionSyncMethod = AudioManager.class.getDeclaredMethod("remoteControlDisplayWantsPlaybackPositionSync", iRemoteControlDisplayClass, boolean.class);
            remoteControlDisplayWantsPlaybackPositionSyncMethod.setAccessible(true);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("Method remoteControlDisplayWantsPlaybackPositionSync() doesn't exist, can't access it with reflection");
        }
        
        Object rcDisplay;
        Field rcDisplayField;
        try {
            rcDisplayField = RemoteController.class.getDeclaredField("mRcd");
            rcDisplayField.setAccessible(true);
            rcDisplay = rcDisplayField.get(mRemoteController);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException("Field mRcd doesn't exist, can't access it with reflection");
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Field mRcd can't be accessed - access denied");
        } catch (IllegalArgumentException e) {
            throw new RuntimeException("Field mRcd can't be accessed - invalid argument");
        }
        
        AudioManager am = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
        try {
            remoteControlDisplayWantsPlaybackPositionSyncMethod.invoke(am, iRemoteControlDisplayClass.cast(rcDisplay), true);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Method remoteControlDisplayWantsPlaybackPositionSync() invocation failure - access denied");
        } catch (IllegalArgumentException e) {
            throw new RuntimeException("Method remoteControlDisplayWantsPlaybackPositionSync() invocation failure - invalid arguments");
        } catch (InvocationTargetException e) {
            throw new RuntimeException("Method remoteControlDisplayWantsPlaybackPositionSync() invocation failure - invalid invocation target");
        }
    }
    

    public boolean sendKeyEvent(int keyCode) {
        //send "down" and "up" keyevents.
        KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
        boolean first = mRemoteController.sendMediaKeyEvent(keyEvent);
        keyEvent = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
        boolean second = mRemoteController.sendMediaKeyEvent(keyEvent);
        
        return first && second; //if both  clicks were delivered successfully
    }
    //end of helper methods.
    
    
    //the most simple Binder implementation
    public class RCBinder extends Binder {
        public TransportControlService getService() {
            return TransportControlService.this;
        }
    }

    //implementation of RemoteController.OnClientUpdateListener. Does nothing other than calling external callback.
    @Override
    public void onClientChange(boolean arg0) {
        if(mExternalClientUpdateListener != null) {
            mExternalClientUpdateListener.onClientChange(arg0);
        }
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    @Override
    public void onClientMetadataUpdate(MetadataEditor metadataEditor) {
        if(mExternalClientUpdateListener != null) {
            Bundle metadata = new Bundle();
            
            String artist = metadataEditor.getString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, null);
            if (artist != null) {
                metadata.putString(TransportControlView.Metadata.METADATA_KEY_ALBUMARTIST, artist);
            }
            String trackTitle = metadataEditor.getString(MediaMetadataRetriever.METADATA_KEY_TITLE, null);
            if (trackTitle != null) {
                metadata.putString(TransportControlView.Metadata.METADATA_KEY_TITLE, trackTitle);
            }
            String albumTitle = metadataEditor.getString(MediaMetadataRetriever.METADATA_KEY_ALBUM, null);
            if (albumTitle != null) {
                metadata.putString(TransportControlView.Metadata.METADATA_KEY_ALBUM, albumTitle);
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                Bitmap bitmap = metadataEditor.getBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK, null);
                if (bitmap != null) {
                    metadata.putParcelable(TransportControlView.Metadata.BITMAP_KEY_ARTWORK, bitmap);
                }
            }
            mExternalClientUpdateListener.onClientMetadataUpdate(metadata);
        }
    }

    @Override
    public void onClientPlaybackStateUpdate(int arg0) {
        if(mExternalClientUpdateListener != null) {
            mExternalClientUpdateListener.onClientPlaybackStateUpdate(arg0);
        }
    }

    @Override
    public void onClientPlaybackStateUpdate(int arg0, long arg1, long arg2, float arg3) {
        if(mExternalClientUpdateListener != null) {
            mExternalClientUpdateListener.onClientPlaybackStateUpdate(arg0, arg1, arg2, arg3);
        }
    }

    @Override
    public void onClientTransportControlUpdate(int arg0) {
        if(mExternalClientUpdateListener != null) {
            mExternalClientUpdateListener.onClientTransportControlUpdate(arg0);
        }
        
    }
}
